/*
 * Licensed to the Tai Ping Jin Ke
 *
 * Copyright (c) 2022 .
 * All rights reserved.
 * 项目名称：太平金科-通用能力封装层-简化POI的原生API使用
 * 版权说明：本软件属太平金融科技服务(上海)有限公司所有，在未获得太平金融科技服务(上海)有限公司正式授权情况下，任何企业和个人，不能获取、阅读、安装、传播本软件涉及的任何受知识产权保护的内容。
 */
package com.taipingframework.poi;

import java.beans.IntrospectionException;
import java.beans.PropertyDescriptor;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import com.taipingframework.utility.thread.ParallelHelper;
import lombok.extern.slf4j.Slf4j;

/**
 * 描述：用于将 Java POJO 的对象或集合转换为适配于Excel的数据结构
 *
 * @author 周孝兵
 * @since 2022-02-28
 */
@Slf4j
@SuppressWarnings("unused")
public class ExcelConverter {

    /**
     * 将Excel的单元格以'数据行'为单位转换为 Java POJO 的集合
     *
     * @param dataGridRowList 列表数据
     * @param clazz           Java POJO 的引用类型（形如 T.class）
     * @param headerSize      表头占了几行
     */
    public static <T extends Object & Serializable> List<T> from(final List<TreeMap<String, String>> dataGridRowList,
                                                                 final Class<T> clazz, final int headerSize) {
        final CopyOnWriteArrayList<T> javaPOJOList = new CopyOnWriteArrayList<>();
        ParallelHelper.parallelHandle(dataGridRowList, (TreeMap<String, String> dataGridRow) -> {
            if (dataGridRowList.indexOf(dataGridRow) <= headerSize - 1) { // 排除表头
                log.info("第一行默认为标题，不参与数据格式的转换，如果导入的Excel模板的标题占了2行(存在纵向合并单元格的情况，如二级表头或者三级表头)，则此处的条件为‘dataGridRowList.indexOf(dataGridRow) <= 1’");
                return;
            }
            T javaPOJO = ExcelConverter.from(dataGridRow, clazz);
            javaPOJOList.add(javaPOJO); // 添加 Java POJO 对象
        });
        return javaPOJOList;
    }

    /**
     * 将Excel的单元格以'数据行'为单位转换为 Java POJO
     */
    public static <T extends Object & Serializable> T from(TreeMap<String, String> dataGridRow, Class<T> clazz) {
        T instance = null;

        // 获取 Java POJO 数据成员 与 Excel 每列单元格的关系
        TreeMap<String, Field> fieldTreeMap = ExcelConverter.getHeaderTreeMap(clazz);

        try {
            instance = clazz.newInstance();
        } catch (InstantiationException | IllegalAccessException e) {
            log.error(e.getMessage(), e);
        }
        if (instance == null) {
            return null;
        }

        // 逐个单元格进行转换
        for (Map.Entry<String, String> entry : dataGridRow.entrySet()) {
            String cellAddress = entry.getKey(); // 单元格的地址
            String cellContent = entry.getValue();
            // 解析坐标
            CoordinateParser coordinateParser = new CoordinateParser(cellAddress);
            // 获取列索引
            int columnIndex = coordinateParser.getColumn();
            // 获取列名
            String cellTagOnAxis = CoordinateParser.getCellTagOnAxis(columnIndex);
            // 根据X轴坐标获取对应的字段
            Field field = fieldTreeMap.get(cellTagOnAxis);
            // 为字段赋值
            setJavaPOJOProperty(field, cellContent, instance, clazz);
        }

        return instance;
    }

    /**
     * 将 Java POJO 的集合 转换为 以'数据行'为单位Excel工作薄
     *
     * @param javaPOJOList  数据列表
     * @param clazz         Java POJO 的引用类型（形如 T.class）
     * @param includeHeader 是否包含表头
     * @param headerSize    表头部分占用几行
     */
    public static <T extends Object & Serializable> List<TreeMap<String, String>> to(final List<T> javaPOJOList,
                                                                                     final Class<T> clazz, boolean includeHeader, int headerSize) {
        final CopyOnWriteArrayList<TreeMap<String, String>> dataGridRowList = new CopyOnWriteArrayList<>();

        // 如果是复杂表头处理起来则会比较繁琐，需要对注解@ExcelProperty的功能进行扩展
        if (includeHeader) {
            TreeMap<String, String> titleDataGridRow = new TreeMap<>();
            TreeMap<String, String> titleTreeMap = Stream.of(clazz.getDeclaredFields())
                    .filter(field -> field.getAnnotation(ExcelProperty.class) != null)
                    .collect(
                            Collectors.toMap(
                                    field -> field.getAnnotation(ExcelProperty.class).axisX(),
                                    field -> field.getAnnotation(ExcelProperty.class).title(),
                                    (oldValue, newValue) -> newValue,
                                    () -> new TreeMap<>(new CoordinateComparator())
                            )
                    );
            int axisY = 1; // 默认表头占一行
            for (Map.Entry<String, String> entry : titleTreeMap.entrySet()) {
                String axisX = entry.getKey();
                titleDataGridRow.put(axisX + axisY, entry.getValue());
            }
            dataGridRowList.add(titleDataGridRow); // 添加表头
        }

        ParallelHelper.parallelHandle(javaPOJOList, (T javaPOJO) -> {
            int rowIndex = javaPOJOList.indexOf(javaPOJO);
            log.debug("第一行默认为标题，复杂标题(存在纵向合并单元格的情况，如二级表头或者三级表头)需要进一步定制化开发或者使用Excel模板导出，这里需要单独设置Excel的表头部分");

            TreeMap<String, String> dataGridRow = ExcelConverter.to(javaPOJO, rowIndex + (headerSize + 1), clazz); // 默认表头占一行，数据要从第二行开始写入Sheet页
            dataGridRowList.add(dataGridRow); // 添加数据行
        });
        return dataGridRowList;
    }

    /**
     * 将 Java POJO 转换为 Excel工作薄的'数据行'
     */
    static <T extends Object & Serializable> TreeMap<String, String> to(T javaPOJO, Integer rowIndex,
                                                                        final Class<T> clazz) {
        // 获取 Java POJO 数据成员 与 Excel 每列单元格的关系
        TreeMap<String, Field> fieldTreeMap = ExcelConverter.getHeaderTreeMap(clazz);
        TreeMap<String, String> dataGridRow = new TreeMap<>();
        for (Map.Entry<String, Field> entry : fieldTreeMap.entrySet()) {
            String cellAddress = entry.getKey(); // X 坐标轴的地址（表示哪一列）
            Field field = entry.getValue();
            Object value = ExcelConverter.getJavaPOJOProperty(field, javaPOJO, clazz);
            dataGridRow.put(cellAddress + rowIndex, value == null ? "" : String.valueOf(value));
        }
        return dataGridRow;
    }

    /**
     * 将 Java POJO 转换为 Excel工作薄的'数据行'
     */
    public static <T extends Object & Serializable> TreeMap<String, String> to(T javaPOJO, final Class<T> clazz) {
        // 获取 Java POJO 数据成员 与 Excel 每列单元格的关系
        TreeMap<String, Field> fieldTreeMap = Stream.of(clazz.getDeclaredFields())
                .filter(field -> field.getAnnotation(ExcelProperty.class) != null)
                .collect(
                        Collectors.toMap(
                                field -> field.getAnnotation(ExcelProperty.class).address(),
                                field -> field,
                                (oldValue, newValue) -> newValue,
                                () -> new TreeMap<>(new CoordinateComparator())
                        )
                );
        TreeMap<String, String> dataGridRow = new TreeMap<>();
        for (Map.Entry<String, Field> entry : fieldTreeMap.entrySet()) {
            String cellAddress = entry.getKey(); // 单元格的地址（在指定Sheet页上能够表示唯一确定的单元格）
            Field field = entry.getValue();
            Object value = ExcelConverter.getJavaPOJOProperty(field, javaPOJO, clazz);
            dataGridRow.put(cellAddress, value == null ? "" : String.valueOf(value));
        }
        return dataGridRow;
    }

    /**
     * 为字段赋值
     */
    static <T extends Object & Serializable> void setJavaPOJOProperty(Field field, String cellContent, T instance,
                                                                      Class<T> clazz) {
        try {
            if (field == null) {
                return;
            }

            Object value;
            Class<?> type = field.getType();
            boolean typeCondition = type == Short.class
                    || type == Integer.class
                    || type == Long.class
                    || type == Double.class
                    || type == Float.class
                    || type == Boolean.class
                    || type == Byte.class
                    || type == Character.class;
            if (typeCondition) {
                // 转换为 8种基本数据类型
                value = type.cast(cellContent);
            } else if (type == Date.class) {
                // 转换为 日期类型，这里比较复杂，最好使用函数式接口处理各种场景
                value = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(cellContent);
            } else if (type == String.class) {
                // 转换为 字符串类型
                value = cellContent;
            } else if (type == BigDecimal.class) {
                // 转换为 BigDecimal类型
                value = new BigDecimal(cellContent);
            } else {
                // 默认不支持Java对象，除非使用函数式接口在此处进行支持，例如可以直接在这里把JSON字符串转换为具体的Java对象
                throw new RuntimeException("不支持的数据类型：" + type.getName());
            }
            PropertyDescriptor descriptor = new PropertyDescriptor(field.getName(), clazz);
            Method writeMethod = descriptor.getWriteMethod();
            writeMethod.invoke(instance, value);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
    }

    /**
     * 获取字段值
     */
    static <T extends Object & Serializable> Object getJavaPOJOProperty(Field field, T javaPOJO, Class<T> clazz) {
        try {
            if (field == null) {
                return "";
            }

            PropertyDescriptor descriptor = new PropertyDescriptor(field.getName(), clazz);
            Method readMethod = descriptor.getReadMethod();
            return readMethod.invoke(javaPOJO);
        } catch (IllegalAccessException | IntrospectionException | InvocationTargetException e) {
            log.error(e.getMessage(), e);
            return "";
        }
    }

    /**
     * 获取Excel单元格与 Java POJO 数据成员的映射关系
     */
    static <T extends Object & Serializable> TreeMap<String, Field> getHeaderTreeMap(final Class<T> clazz) {
        return Stream.of(clazz.getDeclaredFields())
                .filter(field -> field.getAnnotation(ExcelProperty.class) != null)
                .collect(
                        Collectors.toMap(
                                field -> field.getAnnotation(ExcelProperty.class).axisX(),
                                field -> field,
                                (oldValue, newValue) -> newValue,
                                () -> new TreeMap<>(new CoordinateComparator())
                        )
                );
    }

}
